/************************************************************************
 ************************* BFS TASK *************************************
 ************************************************************************
 ************************************************************************/

#include <BFSTask.h>
// Default Constructor
BFSTask_t::BFSTask_t() {
    double t2;
    double t1;
    timeval tim;
    Q           = NULL;
    sem_init(&mutex, 0, 0);
    pthread_mutex_init(&qmutex, NULL);
    pthread_mutex_init(&gmutex, NULL);
    pthread_mutex_init(&vmutex, NULL);
    //pthread_mutex_init(&visited_mutex, NULL);
    pthread_mutex_init(&cmutex,NULL);
    initial = true;
    count = 0;
    position = 0;
    num_vertices = 0;
}

// Constructor 
BFSTask_t::BFSTask_t(PPPGraph g, WorkQueue * _Q, int g_size) {
    double t2;
    double t1;
    timeval tim;
    graph       = g;
    Q           = _Q;
    BFS.graph= new PPPGraph();
    BFS.visited.resize(g_size);
    sem_init(&mutex, 0, 0);
    pthread_mutex_init(&qmutex,NULL);
    pthread_mutex_init(&gmutex, NULL); 
    pthread_mutex_init(&vmutex,NULL);
    //pthread_mutex_init(&visited_mutex,NULL);
    for (int i=0; i< g_size; i++ ) {
      //   pthread_mutex_init(&visited_mutex,NULL);
      visited_mutex.push_back(new pthread_mutex_t);
      pthread_mutex_init(visited_mutex[i],NULL);
    }
    pthread_mutex_init(&cmutex,NULL);
    initial = true;
    count = 0;
    position=0;
    num_vertices = g_size;

    for (int i = 0 ; i < g_size; i++){
        BFS.graph->nodes.push_back(new PPPNode());
    }
}

// Destructor
BFSTask_t::~BFSTask_t() {
    Q     = NULL;
    sem_destroy(&mutex);
    pthread_mutex_destroy(&qmutex);
    pthread_mutex_destroy(&gmutex);
    pthread_mutex_destroy(&vmutex);
    //pthread_mutex_destroy(&visited_mutex);
    for (int i=0; i< visited_mutex.size(); i++ ) {
      pthread_mutex_destroy(visited_mutex[i]);
    }
    pthread_mutex_destroy(&cmutex);
}

// init method
void BFSTask_t::init() {
    timeval tim;
    gettimeofday(&tim, NULL);
    t1=tim.tv_sec+(tim.tv_usec/1000000.0);
    BFS.visited.setBit(0) ; //mark root as visited
    //printf("Position is %d\n", position);
    (BFS.graph->nodes)[position]->id= 0;// first node to
    position++;
    for (int i=0; i< graph.edges[0]->size();i++)
    {
        int neig_id =(*graph.edges[0])[i].to; // get neighbor id
        bool vis = false;
        pthread_mutex_lock(visited_mutex[neig_id]);
        if (!BFS.visited.getBit(neig_id)) {
            BFS.visited.setBit(neig_id);
            vis = false;
        } else {
            vis = true;
        }
        pthread_mutex_unlock(visited_mutex[neig_id]);
        if (vis)
            continue;
        pthread_mutex_lock(&gmutex);
        BFS.graph->nodes[position]->id=neig_id;// put it as first neighbor
        position++;	// update position
        pthread_mutex_unlock(&gmutex);
        // create a merge task
        MergeTask_t * mymerge = new MergeTask_t(&graph, neig_id, &mutex, &vmutex, &qmutex, &visited_mutex, &BFS, &count, &cmutex, &position, &vqueue, &gmutex, Q);
        // add it to the task
        Q->addTask(mymerge);
        // update task
        pthread_mutex_lock(&cmutex);
        count++;
        pthread_mutex_unlock(&cmutex);
    }
    initial=false;
}

// run method
void BFSTask_t::run() {
    if (initial) 
        init(); // call initialize function

    pthread_mutex_lock(&cmutex);
    if (count == 0) { // done
        pthread_mutex_unlock(&cmutex);
        gettimeofday(&tim, NULL);
        t2=tim.tv_sec+(tim.tv_usec/1000000.0);	
        printf("%.6lf \t %d\n", t2 - t1, num_vertices);    
        doneState = Done;
#if 0
        for (int k=0;k<position;k++)
        {
            std::cout<<"-------> "<<BFS.graph->nodes[k]->id<<endl;
        } 
#endif
    } else {// still working
        pthread_mutex_unlock(&cmutex);
        doneState = Working;
    }
}

///////////////////////// END OF BFSTASK ////////////////////////////////

/************************************************************************\
 *********************** MERGE TASK *************************************
 ************************************************************************
 ************************************************************************/

// default constructor
MergeTask_t::MergeTask_t() {
    graph       = NULL;
    vqueue      = NULL;
    Q           = NULL;
}

// constructor 
MergeTask_t::MergeTask_t(PPPGraph	 * _graph,
        int		  _vertex_id,
        sem_t		 * _mutex,
        pthread_mutex_t * _vmutex,
        pthread_mutex_t * _qmutex,
	std::vector<pthread_mutex_t*> * _visited_mutex,
        Traverse <PPPGraph> * _BFS,
        int		 * _count,
        pthread_mutex_t * _cmutex,
        int		 * _position,
        std::queue <int> * _vqueue,
        pthread_mutex_t * _gmutex,
        WorkQueue * _Q
        )
{
    graph            = _graph;
    gmutex           = _gmutex;
    mutex		     = _mutex;
    vmutex           = _vmutex;
    qmutex           = _qmutex;
    vertex_id	     = _vertex_id;
    BFS		         = _BFS;
    visited_mutex    = _visited_mutex;
    cmutex           = _cmutex;
    count            = _count;
    position         = _position;
    vqueue		     = _vqueue;
    Q                = _Q;
}

MergeTask_t::~MergeTask_t() { 
    graph       = NULL;
    vqueue      = NULL;
    Q           = NULL;
}

// run method
void MergeTask_t::run() {
    int curr = -1;
    for (int i=0; i< graph->edges[vertex_id]->size();i++) {
        int neig =(*graph->edges[vertex_id])[i].to;
        bool vis;
        pthread_mutex_lock(visited_mutex->at(neig));
        if (vis = BFS->visited.getBit(neig)) {
	  pthread_mutex_unlock(visited_mutex->at(neig));
        }
        else { // not visited
            BFS->visited.setBit(neig); 
            pthread_mutex_unlock(visited_mutex->at(neig));
            pthread_mutex_lock(gmutex);
            (BFS->graph->nodes)[(*position)++]->id=neig;// first node to
            pthread_mutex_unlock(gmutex);
            // mymerge
            // add it to the task
            MergeTask_t * mymerge = new MergeTask_t(graph, neig, mutex, vmutex, qmutex, visited_mutex, BFS, count, cmutex, position, vqueue, gmutex, Q);
            Q->addTask(mymerge);
            curr++;
        }
    }
    pthread_mutex_lock(cmutex);
    *count = *count + curr;
    pthread_mutex_unlock(cmutex);
    Task::SetState(Done);
}
